/*
 * Created on Jul 16, 2008
 * Created by Paul Gardner
 * 
 * Copyright (C) Azureus Software, Inc, All Rights Reserved.
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License only.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
 */

package com.aelitis.azureus.core.lws;

import java.io.File;
import java.net.InetSocketAddress;
import java.net.URL;

import org.gudy.azureus2.core3.disk.DiskManager;
import org.gudy.azureus2.core3.disk.DiskManagerReadRequest;
import org.gudy.azureus2.core3.disk.DiskManagerReadRequestListener;
import org.gudy.azureus2.core3.logging.LogEvent;
import org.gudy.azureus2.core3.logging.LogIDs;
import org.gudy.azureus2.core3.logging.LogRelation;
import org.gudy.azureus2.core3.logging.Logger;
import org.gudy.azureus2.core3.peer.PEPeer;
import org.gudy.azureus2.core3.peer.PEPeerManager;
import org.gudy.azureus2.core3.peer.PEPeerManagerFactory;
import org.gudy.azureus2.core3.peer.PEPeerManagerListener;
import org.gudy.azureus2.core3.peer.PEPiece;
import org.gudy.azureus2.core3.peer.util.PeerUtils;
import org.gudy.azureus2.core3.torrent.TOTorrent;
import org.gudy.azureus2.core3.tracker.client.TRTrackerAnnouncer;
import org.gudy.azureus2.core3.tracker.client.TRTrackerAnnouncerDataProvider;
import org.gudy.azureus2.core3.tracker.client.TRTrackerAnnouncerException;
import org.gudy.azureus2.core3.tracker.client.TRTrackerAnnouncerFactory;
import org.gudy.azureus2.core3.tracker.client.TRTrackerAnnouncerListener;
import org.gudy.azureus2.core3.tracker.client.TRTrackerAnnouncerResponse;
import org.gudy.azureus2.core3.tracker.client.TRTrackerAnnouncerResponsePeer;
import org.gudy.azureus2.core3.util.ByteFormatter;
import org.gudy.azureus2.core3.util.HashWrapper;
import org.gudy.azureus2.core3.util.SystemTime;
import org.gudy.azureus2.plugins.torrent.Torrent;
import org.gudy.azureus2.pluginsimpl.local.torrent.TorrentImpl;

import com.aelitis.azureus.core.networkmanager.NetworkConnection;
import com.aelitis.azureus.core.networkmanager.NetworkManager;
import com.aelitis.azureus.core.peermanager.PeerManager;
import com.aelitis.azureus.core.peermanager.PeerManagerRegistration;
import com.aelitis.azureus.core.peermanager.PeerManagerRegistrationAdapter;
import com.aelitis.azureus.core.peermanager.peerdb.PeerItem;

public class LightWeightSeed extends LogRelation implements PeerManagerRegistrationAdapter {
    private static final byte ACT_NONE = 0;
    private static final byte ACT_HAS_PEERS = 2;
    private static final byte ACT_HAS_POTENTIAL_PEERS = 3;
    private static final byte ACT_INCOMING = 4;
    private static final byte ACT_NO_PM = 5;
    private static final byte ACT_TIMING_OUT = 6;
    private static final byte ACT_TRACKER_ANNOUNCE = 7;
    private static final byte ACT_TRACKER_SCRAPE = 8;

    private static final int DEACTIVATION_TIMEOUT = 5 * 60 * 1000;
    private static final int DEACTIVATION_WITH_POTENTIAL_TIMEOUT = 15 * 60 * 1000;

    private LightWeightSeedManager manager;
    private LightWeightSeedAdapter adapter;
    private String name;
    private HashWrapper hash;
    private URL announce_url;
    private File data_location;

    private PeerManagerRegistration peer_manager_registration;

    private volatile PEPeerManager peer_manager;
    private volatile LWSDiskManager disk_manager;
    private LWSDownload pseudo_download;
    private volatile LWSTorrent torrent_facade;
    private TRTrackerAnnouncer announcer;

    private TOTorrent actual_torrent;

    private boolean is_running;
    private long last_activity_time;
    private int activation_state = ACT_NONE;

    protected LightWeightSeed(LightWeightSeedManager _manager, String _name, HashWrapper _hash, URL _announce_url, File _data_location,
            LightWeightSeedAdapter _adapter) {
        manager = _manager;
        name = _name;
        hash = _hash;
        announce_url = _announce_url;
        data_location = _data_location;
        adapter = _adapter;
    }

    protected String getName() {
        return (name + "/" + ByteFormatter.encodeString(hash.getBytes()));
    }

    protected Torrent getTorrent() {
        return (new TorrentImpl(getTOTorrent(false)));
    }

    protected TOTorrent getTOTorrent(boolean actual) {
        if (actual) {

            synchronized (this) {

                if (actual_torrent == null) {

                    try {

                        actual_torrent = adapter.getTorrent(hash.getBytes(), announce_url, data_location);

                    } catch (Throwable e) {

                        log("Failed to get torrent", e);
                    }

                    if (actual_torrent == null) {

                        throw (new RuntimeException("Torrent not available"));
                    }
                }

                return (actual_torrent);
            }
        } else {

            return (torrent_facade);
        }
    }

    public HashWrapper getHash() {
        return (hash);
    }

    public URL getAnnounceURL() {
        return (announce_url);
    }

    public File getDataLocation() {
        return (data_location);
    }

    protected long getSize() {
        return (data_location.length());
    }

    public boolean isPeerSourceEnabled(String peer_source) {
        return (true);
    }

    public boolean manualRoute(NetworkConnection connection) {
        return false;
    }

    public byte[][] getSecrets() {
        return (new byte[][] { hash.getBytes() });
    }

    public boolean activateRequest(InetSocketAddress remote_address) {
        ensureActive("Incoming[" + remote_address.getAddress().getHostAddress() + "]", ACT_INCOMING);

        return (true);
    }

    public void deactivateRequest(InetSocketAddress remote_address) {

    }

    public String getDescription() {
        return ("LWS: " + getName());
    }

    protected synchronized void start() {
        log("Start");

        if (is_running) {

            log("Start of '" + getString() + "' failed - already running");

            return;
        }

        if (peer_manager_registration != null) {

            log("Start of '" + getString() + "' failed - router already registered");

            return;
        }

        if (pseudo_download != null) {

            log("Start of '" + getString() + "' failed - pseudo download already registered");

            return;
        }

        if (disk_manager != null) {

            log("Start of '" + getString() + "' failed - disk manager already started");

            return;
        }

        if (peer_manager != null) {

            log("Start of '" + getString() + "' failed - peer manager already started");

            return;
        }

        try {
            if (torrent_facade == null) {

                torrent_facade = new LWSTorrent(this);
            }

            peer_manager_registration = PeerManager.getSingleton().registerLegacyManager(hash, this);

            announcer = createAnnouncer();

            pseudo_download = new LWSDownload(this, announcer);

            manager.addToDHTTracker(pseudo_download);

            is_running = true;

            last_activity_time = SystemTime.getMonotonousTime();

        } catch (Throwable e) {

            log("Start of '" + getString() + "' failed", e);

        } finally {

            if (is_running) {

                log("Started " + getString());

            } else {

                stop();
            }
        }
    }

    protected synchronized void stop() {
        log("Stop");

        try {
            if (disk_manager != null) {

                disk_manager.stop(false);

                disk_manager = null;
            }

            if (peer_manager != null) {

                peer_manager.stopAll();

                peer_manager = null;
            }

            if (pseudo_download != null) {

                manager.removeFromDHTTracker(pseudo_download);

                pseudo_download = null;
            }

            if (announcer != null) {

                announcer.stop(false);

                announcer.destroy();

                announcer = null;
            }

            if (peer_manager_registration != null) {

                peer_manager_registration.unregister();

                peer_manager_registration = null;
            }

        } finally {

            is_running = false;

            activation_state = ACT_NONE;

            log("Stopped " + getString());
        }
    }

    protected synchronized void activate(String reason_str, byte activation_reason) {
        log("Activate: " + activation_reason + "/" + reason_str);

        if (activation_state != ACT_NONE) {

            return;
        }

        try {
            disk_manager = new LWSDiskManager(this, data_location);

            disk_manager.start();

            if (disk_manager.getState() != DiskManager.READY) {

                log("Start of '" + getString() + "' failed, disk manager failed = " + disk_manager.getErrorMessage());

            } else {

                peer_manager =
                        PEPeerManagerFactory
                                .create(announcer.getPeerId(), new LWSPeerManagerAdapter(this, peer_manager_registration), disk_manager);

                peer_manager.addListener(new PEPeerManagerListener() {
                    public void peerAdded(final PEPeerManager manager, final PEPeer peer) {
                        last_activity_time = SystemTime.getMonotonousTime();
                    }

                    public void peerRemoved(PEPeerManager manager, PEPeer peer) {
                        last_activity_time = SystemTime.getMonotonousTime();
                    }

                    public void peerDiscovered(PEPeerManager manager, PeerItem peer, PEPeer finder) {
                    }

                    public void pieceAdded(PEPeerManager manager, PEPiece peice, PEPeer for_peer) {
                    }

                    public void pieceRemoved(PEPeerManager manager, PEPiece peice) {
                    }

                    public void peerSentBadData(PEPeerManager manager, PEPeer peer, int pieceNumber) {
                    }

                    public void destroyed() {
                    }
                });

                peer_manager.start();

                announcer.update(true);

                activation_state = activation_reason;

                last_activity_time = SystemTime.getMonotonousTime();
            }

        } catch (Throwable e) {

            log("Activation of '" + getString() + "' failed", e);

        } finally {

            if (activation_state != ACT_NONE) {

            } else {

                deactivate();
            }
        }
    }

    protected synchronized void deactivate() {
        log("Deactivate");

        try {
            if (disk_manager != null) {

                disk_manager.stop(false);

                disk_manager = null;
            }

            if (peer_manager != null) {

                peer_manager.stopAll();

                peer_manager = null;
            }
        } finally {

            activation_state = ACT_NONE;
        }
    }

    protected synchronized TRTrackerAnnouncer createAnnouncer() throws TRTrackerAnnouncerException {

        // use a facade here to delay loading the actual torrent until the
        // download is activated

        TRTrackerAnnouncer result = TRTrackerAnnouncerFactory.create(torrent_facade, true);

        result.addListener(new TRTrackerAnnouncerListener() {
            public void receivedTrackerResponse(TRTrackerAnnouncerResponse response) {
                TRTrackerAnnouncerResponsePeer[] peers = response.getPeers();

                // tracker shouldn't return seeds to seeds to we can assume
                // that if peers returned this means we have someone to talk to

                if (peers != null && peers.length > 0) {

                    ensureActive("Tracker[" + peers[0].getAddress() + "]", ACT_TRACKER_ANNOUNCE);

                } else if (response.getScrapeIncompleteCount() > 0) {

                    ensureActive("Tracker[scrape]", ACT_TRACKER_SCRAPE);
                }

                PEPeerManager pm = peer_manager;

                if (pm != null) {

                    pm.processTrackerResponse(response);
                }
            }

            public void urlChanged(TRTrackerAnnouncer announcer, URL old_url, URL new_url, boolean explicit) {
            }

            public void urlRefresh() {
            }
        });

        result.setAnnounceDataProvider(new TRTrackerAnnouncerDataProvider() {
            public String getName() {
                return (LightWeightSeed.this.getName());
            }

            public long getTotalSent() {
                return (0);
            }

            public long getTotalReceived() {
                return (0);
            }

            public long getFailedHashCheck() {
                return (0);
            }

            public long getRemaining() {
                return (0);
            }

            public String getExtensions() {
                return (null);
            }

            public int getMaxNewConnectionsAllowed() {
                PEPeerManager pm = peer_manager;

                if (pm == null) {

                    // got to ask for at least one to trigger activation!

                    return (8);
                }

                return (PeerUtils.numNewConnectionsAllowed(pm.getPeerIdentityDataID(), 0));
            }

            public int getPendingConnectionCount() {
                PEPeerManager pm = peer_manager;

                if (pm == null) {

                    return (0);
                }

                return (pm.getPendingPeerCount());
            }

            public int getConnectedConnectionCount() {
                PEPeerManager pm = peer_manager;

                if (pm == null) {

                    return (0);
                }

                return (pm.getNbPeers() + pm.getNbSeeds());
            }

            public int getUploadSpeedKBSec(boolean estimate) {
                return 0;
            }

            public int getCryptoLevel() {
                return (NetworkManager.CRYPTO_OVERRIDE_NONE);
            }

            public boolean isPeerSourceEnabled(String peer_source) {
                return (true);
            }

            public void setPeerSources(String[] sources) {
            }
        });

        return (result);
    }

    protected synchronized void ensureActive(String reason, byte a_reason) {
        if (is_running && activation_state == ACT_NONE) {

            activate(reason, a_reason);
        }
    }

    protected synchronized void checkDeactivation() {
        if (activation_state == ACT_NONE) {

            return;
        }

        if (peer_manager == null) {

            activation_state = ACT_NO_PM;

            return;
        }

        if (peer_manager.getNbPeers() > 0) {

            activation_state = ACT_HAS_PEERS;

            return;
        }

        long now = SystemTime.getMonotonousTime();

        long millis_since_last_act = now - last_activity_time;

        if (peer_manager.hasPotentialConnections()) {

            if (millis_since_last_act < DEACTIVATION_WITH_POTENTIAL_TIMEOUT) {

                activation_state = ACT_HAS_POTENTIAL_PEERS;

                return;
            }
        }

        if (millis_since_last_act >= DEACTIVATION_TIMEOUT) {

            deactivate();

        } else {

            activation_state = ACT_TIMING_OUT;
        }
    }

    public void enqueueReadRequest(PEPeer peer, DiskManagerReadRequest request, DiskManagerReadRequestListener listener) {
        LWSDiskManager dm = disk_manager;

        if (dm == null) {

            listener.readFailed(request, new Throwable("download is stopped"));

        } else {

            dm.enqueueReadRequest(request, listener);
        }
    }

    public void remove() {
        manager.remove(this);
    }

    public String getRelationText() {
        return "LWS: '" + getName() + "'";
    }

    public Object[] getQueryableInterfaces() {
        return new Object[] {};
    }

    public LogRelation getRelation() {
        return (this);
    }

    protected String getString() {
        return (getName());
    }

    protected void log(String str) {
        Logger.log(new LogEvent(this, LogIDs.CORE, str));
    }

    protected void log(String str, Throwable e) {
        Logger.log(new LogEvent(this, LogIDs.CORE, str, e));
    }
}
